Fields and Methods<Home> |
|---|
Now that you know how the JNI lets native code access primitive types and reference types such as strings and arrays, the next step will be to learn how to interact with fields and methods in arbitrary objects. In addition to accessing fields, this includes making calls to methods implemented in the Java programming language from native code, commonly known as performing callbacks from native code.
We will begin by introducing the JNI functions that support field access and method callbacks. Later in this chapter we will discuss how to make such operations more efficient by using a simple but effective caching technique. In the last section, we will discuss the performance characteristics of calling native methods as well as accessing fields and calling methods from native code.
The Java programming language supports two kinds of fields. Each instance of a class has its own copy of the instance fields of the class, whereas all instances of a class share the static fields of the class.
The JNI provides functions that native code can use to get and set instance fields in objects and static fields in classes. Let us first look at an example program that illustrates how to access instance fields from a native method implementation.
class InstanceFieldAccess
{ private String s;
private native void accessField();
public static void main(String args[])
{ InstanceFieldAccess c = new InstanceFieldAccess();
c.s = "abc";
c.accessField();
System.out.println("In Java:");
System.out.println(" c.s = \"" + c.s + "\"");
}
static { System.loadLibrary("InstanceFieldAccess");
}
}
The InstanceFieldAccess class defines an instance field s. The main method creates an object, sets the instance field, and then calls the native method InstanceFieldAccess.accessField. As we will see shortly, the native method prints out the existing value of the instance field and then sets the field to a new value. The program prints the field value again after the native method returns, demonstrating that the field value has indeed changed.
Here is the implementation of the InstanceFieldAccess.accessField native method.
JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj)
{ jfieldID fid; /* store the field ID */
jstring jstr;
const char *str; /* Get a reference to obj's class */
jclass cls = (*env)->GetObjectClass(env, obj);
printf("In C:\n"); /* Look for the instance field s in cls */
fid = (*env)->GetFieldID(env, cls, "s","Ljava/lang/String;");
if (fid == NULL) {
return; /* failed to find the field */
}
/* Read the instance field s */
jstr = (*env)->GetObjectField(env, obj, fid);
str = (*env)->GetStringUTFChars(env, jstr, NULL);
if (str == NULL) {
return; /* out of memory */ }
printf(" c.s = \"%s\"\n", str);
(*env)->ReleaseStringUTFChars(env, jstr, str);
/* Create a new string and overwrite the instance field */
jstr = (*env)->NewStringUTF(env, "123");
if (jstr == NULL) {
return; /* out of memory */
}
(*env)->SetObjectField(env, obj, fid, jstr);
}
Running the InstanceFieldAccess class with the InstanceFieldAccess native library produces the following output:
In C: c.s = "abc" In Java: c.s = "123"
To access an instance field, the native method follows a two-step process. First, it calls GetFieldID to obtain the field ID from the class reference, field name, and field descriptor:
fid = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;");
The example code obtains the class reference cls by calling GetObjectClass on the instance reference obj, which is passed as the second argument to the native method implementation.
Once you have obtained the field ID, you can pass the object reference and the field ID to the appropriate instance field access function:
jstr = (*env)->GetObjectField(env, obj, fid);
Because strings and arrays are special kinds of objects, we use GetObjectField to access the instance field that is a string. Besides Get/SetObjectField, the JNI also supports other functions such asGetIntField and SetFloatField for accessing instance fields of primitive types.
You might have noticed that in the previous section we used a specially encoded C string "Ljava/lang/String;" to represent a field type in the Java programming language. These C strings are called JNI field descriptors.
The content of the string is determined by the declared type of the field. For example, you represent an int field with "I", a float field with "F", a double field with "D", a boolean field with "Z", and so on.
The descriptor for a reference type, such as java.lang.String, begins with the letter L, is followed by the JNI class descriptor, and is terminated by a semicolon. The "." separators in fully qualified class names are changed to "/" in JNI class descriptors. Thus, you form the field descriptor for a field with type java.lang.String as follows:
"Ljava/lang/String;"
Descriptors for array types consist of the "[" character, followed by the descriptor of the component type of the array. For example, "[I" is the descriptor for the int[] field type. Section contains the details of field descriptors and their matching types in the Java programming language.
You can use the javap tool (shipped with JDK or Java 2 SDK releases) to generate the field descriptors from class files. Normally javap prints out the method and field types in a given class. If you specify the -s option (and the -p option for exposing private members), javap prints JNI descriptors instead:
javap -s -p InstanceFieldAccess
This gives you output containing the JNI descriptors for the field s:
s Ljava/lang/String;
Using the javap tool helps eliminate mistakes that can occur from deriving JNI descriptor strings by hand.
Accessing static fields is similar to accessing instance fields. Let us look at a minor variation of the InstanceFieldAccess example:
class StaticFielcdAccess {
private static int si;
private native void accessField();
public static void main(String args[]) {
StaticFieldAccess c = new StaticFieldAccess();
StaticFieldAccess.si = 100;
c.accessField();
System.out.println("In Java:");
System.out.println(" StaticFieldAccess.si = " + si);
}
static {
System.loadLibrary("StaticFieldAccess");
}
}
The StaticFieldAccess class contains a static integer field si. The Static-FieldAccess.main method creates an object, initializes the static field, and then calls the native methodStaticFieldAccess.accessField. As we will see shortly, the native method prints out the existing value of the static field and then sets the field to a new value. To verify that the field has indeed changed, the program prints the static field value again after the native method returns.
Here is the implementation of the StaticFieldAccess.accessField native method.
JNIEXPORT void JNICALL
Java_StaticFieldAccess_accessField(JNIEnv *env, jobject obj)
{
jfieldID fid; /* store the field ID */
jint si; /* Get a reference to obj's class */
jclass cls = (*env)->GetObjectClass(env, obj);
printf("In C:\n"); /* Look for the static field si in cls */
fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
if (fid == NULL) { return; /* field not found */ }
/* Access the static field si */
si = (*env)->GetStaticIntField(env, cls, fid);
printf(" StaticFieldAccess.si = %d\n", si);
(*env)->SetStaticIntField(env, cls, fid, 200);
}
Running the program with the native library produces the following output:
In C: StaticFieldAccess.si = 100 In Java: StaticFieldAccess.si = 200
There are two differences between how you access a static field and how you access an instance field:
There are several kinds of methods in the Java programming language. Instance methods must be invoked on a specific instance of a class, whereas static methods may be invoked independent of any instance. We will defer the discussion of constructors to the next section.
The JNI supports a complete set of functions that allow you to perform callbacks from native code. The example program below contains a native method that in turn calls an instance method implemented in the Java programming language.
class InstanceMethodCall {
private native void nativeMethod();
private void callback() {
System.out.println("In Java");
}
public static void main(String args[]) {
InstanceMethodCall c = new InstanceMethodCall();
c.nativeMethod();
}
static {
System.loadLibrary("InstanceMethodCall");
}
}
Here is the implementation of the native method:
JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj)
{
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid =
(*env)->GetMethodID(env, cls, "callback", "()V");
if (mid == NULL) {
return; /* method not found */
}
printf("In C\n");
(*env)->CallVoidMethod(env, obj, mid);
}
Running the above program produces the following output:
In C In Java
The Java_InstanceMethodCall_nativeMethod implementation illustrates the two steps required to call an instance method:
Besides the CallVoidMethod function, the JNI also supports method invocation functions with other return types. For example, if the method you called back returned a value of type int, then your native method would use CallIntMethod. Similarly, you can use CallObjectMethod to call methods that return objects, which include java.lang.String instances and arrays.
You can use Call<Type>Method family of functions to invoke interface methods as well. You must derive the method ID from the interface type. The following code segment, for example, invokes theRunnable.run method on a java.lang.Thread instance:
The JNI uses descriptor strings to denote method types in a way similar to how it denotes field types. A method descriptor combines the argument types and the return type of a method. The argument types appear first and are surrounded by one pair of parentheses. Argument types are listed in the order in which they appear in the method declaration. There are no separators between multiple argument types. If a method takes no arguments, this is represented with an empty pair of parentheses. Place the method's return type immediately after the right closing parenthesis for the argument types.
For example, "(I)V" denotes a method that takes one argument of type int and has return type void. "()D" denotes a method that takes no arguments and returns